﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Runtime.Serialization;

/* disables warning on Interlocked.[In|De]crement(ref _count) */
#pragma warning disable 0420

namespace Needle.Utils.Threading
{

    /// <summary>
    /// A counted reference to a disposable item.
    /// </summary>
    /// <typeparam name="T">The type of the disposable item.</typeparam>
    /// <example>
    /// The item should be threadsafe or be accessed in a threadsafe way.
    /// <code>
    /// var item = new TDisposable();
    /// var ref = new Reference(item, 2);
    /// 
    /// var thread1 = new Thread( () => 
    /// {
    ///   try
    ///   {
    ///     // Do stuff with ref.Item.
    ///   }
    ///   finally
    ///   {
    ///     ref.Dispose();
    ///   }
    /// });
    /// 
    /// var thread2 = new Thread( () => 
    /// {
    ///   try
    ///   {
    ///     // Do stuff with ref.Item.
    ///   }
    ///   finally
    ///   {
    ///     ref.Dispose();
    ///   }
    /// });
    /// 
    /// thread1.Start();
    /// thread2.Start();
    /// 
    /// try 
    /// {
    ///   using(var myRef = ref.AddRef())
    ///   {
    ///     // Do stuff with myRef.Item.
    ///   }
    /// }
    /// catch (ItemDisposedException)
    /// {
    ///   // thread1 and thread2 finished before this thread 
    ///   // could add a reference
    /// }
    /// 
    /// while (!ref.IsInvalid)
    /// {
    ///   // Very simplistic way to wait until
    ///   // everyone has finished using item.
    ///   Thread.Sleep(100);
    /// }
    /// 
    /// // item has been disposed here.
    /// </code>
    /// </example>
    public class Reference<T> : IDisposable where T : IDisposable
    {

        private readonly T _item;
        private volatile int _count;

        /// <summary>
        /// Creates a new reference to the item. 
        /// Only one reference to the item should be created.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <param name="initialCount">The initial reference count.</param>
        public Reference(T item, int initialCount)
        {
            if (item == null)
                throw new ArgumentNullException("item", "item is null.");
            if (initialCount < 1)
                throw new ArgumentOutOfRangeException("initialCount", "The initialCount must be larger than 0");

            _item = item;
            _count = initialCount;
        }

        /// <summary>
        /// Increments the reference count
        /// </summary>
        /// <exception cref="ItemDisposedException">
        /// Thrown when the item was disposed.
        /// </exception>
        public Reference<T> AddRef()
        {
            if (Interlocked.Increment(ref _count) <= 1)
                throw new ItemDisposedException("Referenced item disposed.");
            return this;
        }

        /// <summary>
        /// Disposing the reference decrements the reference count.
        /// </summary>
        public void Dispose()
        {
            if (Interlocked.Decrement(ref _count) == 0)
                _item.Dispose();
        }

        /// <summary>
        /// <para>Returns true if the item has been disposed.</para>
        /// </summary>
        /// <example>
        /// <para>
        /// Example usage of the IsInvalid property.
        /// </para>
        /// <code>
        /// var ref = new Reference&lt;T&gt;(new T(), 1);
        /// if (ref.IsInvalid)
        /// { 
        ///   // Code to do things when the item is disposed.
        /// } 
        /// else
        /// {
        ///   // Don't do this!
        ///   ref.Item.DoStuff();
        ///   
        ///   // Do this:
        ///   try
        ///   {
        ///     using(var myRef= ref.AddRef())
        ///     {
        ///       myRef.Item.DoStuff();
        ///     }
        ///   } 
        ///   catch (ItemDisposedException)
        ///   {
        ///      // woops, ref was disposed
        ///   }
        /// }
        /// </code>
        /// </example>
        public bool IsInvalid
        {
            get { return _count < 1; }
        }

        /// <summary>
        /// The referenced item.
        /// </summary>
        public T Item
        {
            get { return _item; }
        }
    }

    /// <summary>
    /// Thrown when the item was disposed.
    /// </summary>
    [Serializable]
    public class ItemDisposedException : Exception
    {
        /// <summary>
        /// Constructs a new ItemDisposedException.
        /// </summary>
        public ItemDisposedException() { }
        /// <summary>
        /// Constructs a new ItemDisposedException.
        /// </summary>
        /// <param name="message">The exception message</param>
        public ItemDisposedException(string message) 
            : base(message) { }
        /// <summary>
        /// Constructs a new ItemDisposedException.
        /// </summary>
        /// <param name="message">The exception message</param>
        /// <param name="innerException">The inner exception</param>
        public ItemDisposedException(string message, Exception innerException)
            : base(message, innerException) { }
        /// <summary>
        /// Serialization constructor.
        /// </summary>
        protected ItemDisposedException(SerializationInfo info, StreamingContext context) 
            : base(info, context) { }
    }
}
